package com.jian.springgateway.filter;

import com.jian.project.dubbo.GatewayService;
import com.jian.project.exception.BusinessException;
import com.jian.project.model.dto.tbuserinterface.TbUserInterfaceInvokeRequest;
import com.jian.project.model.entity.DbInterface;
import com.jian.project.model.entity.User;
import com.jian.project.model.enums.InterfaceStatusEnum;
import com.jian.springbootdemosdk.utils.JApiUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.jetbrains.annotations.NotNull;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import static com.jian.project.common.ErrorCode.*;

/**
 * 全局过滤
 */
@Slf4j
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @DubboReference
    private GatewayService gatewayService;

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //请求日志
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String accessKey = headers.getFirst("accessKey");
        InetSocketAddress address = request.getLocalAddress();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        String methodValue = request.getMethodValue();

        log.info("\n请求方式==>{}\t请求路径==>{}\t请求ak==>{}\t请求地址==>{}\t源地址==>{}",
                methodValue, request.getPath().value(), accessKey, address, remoteAddress);
        // TODO（黑白名单）


        String interfaceId = headers.getFirst("interfaceId");
        if (interfaceId == null) {
            ServerHttpResponse response = exchange.getResponse();
            return handleBadRequest(response);
        }

        String body = headers.getFirst("body");
        if (body == null) {
            ServerHttpResponse response = exchange.getResponse();
            return handleBadRequest(response);
        }

        String sign = headers.getFirst("sign");
        if (sign == null) {
            ServerHttpResponse response = exchange.getResponse();
            return handleBadRequest(response);
        }
        String rand = headers.getFirst("rand");
        // 超时检测
        String timestamp = headers.getFirst("timestamp");
        String expiration = headers.getFirst("expiration");
        if (timestamp == null || expiration == null || rand == null
                || System.currentTimeMillis() - Long.parseLong(timestamp) > Long.parseLong(expiration)) {
            ServerHttpResponse response = exchange.getResponse();
            return handleBadRequest(response);
        }

        //请求的模拟接口是否存在？
        DbInterface interfaceInfo = null;
        try {
            interfaceInfo = gatewayService.getDbInterfaceById(Long.parseLong(interfaceId));
        } catch (Exception e) {
            log.error("获取接口信息失败", e);
        }
        if (interfaceInfo == null) {
            ServerHttpResponse response = exchange.getResponse();
            return handleNoAuth(response);
        }
        // 接口是否开启
        if (interfaceInfo.getStatus() == InterfaceStatusEnum.OFFLINE.getValue()){
            throw new BusinessException(SYSTEM_ERROR,"接口已关闭");
        }

        //用户鉴权（判断 ak、sk 是否合法）开始发送请求(全部请求头数据通过校验在发送)
        User user = null;
        try {
            user = gatewayService.getSkByAk(accessKey);
        } catch (Exception e) {
            log.error("获取用户信息失败", e);
        }

        if (user == null || user.getSecurityKey() == null || user.getId() == null) {
            ServerHttpResponse response = exchange.getResponse();
            return handleNoAuth(response);
        }

        // 根据用户id和接口id 判断是否还有剩余次数调用
        Integer remainingInvoke = gatewayService.remainingInvoke(user.getId(), interfaceInfo.getId());
        if (remainingInvoke <= 0) {
            throw new BusinessException(REMAINING_INVOKE_ERROR);
        }

        String securityKey = user.getSecurityKey();
        String signServer = JApiUtils.sign(body, securityKey);
        // 加密sk判断是否相等
        if (!sign.equals(signServer)) {
            ServerHttpResponse response = exchange.getResponse();
            return handleNoAuth(response);
        }


        TbUserInterfaceInvokeRequest invokeRequest = new TbUserInterfaceInvokeRequest();
        invokeRequest.setUserId(user.getId());
        invokeRequest.setInterfaceId(interfaceInfo.getId());
        //请求转发，调用模拟接口
        return responseDecorator(exchange, chain, invokeRequest);
    }

    @Override
    public int getOrder() {
        return -1;
    }

    private Mono<Void> responseDecorator(ServerWebExchange exchange, GatewayFilterChain chain,
                                         TbUserInterfaceInvokeRequest interfaceInvokeRequest) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            HttpStatus statusCode = originalResponse.getStatusCode();

            if (statusCode == HttpStatus.OK) {
                ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                    @Override
                    public @NotNull Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                        if (body instanceof Flux) {
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            Flux<DataBuffer> flux = fluxBody.map(dataBuffer -> {
                                byte[] content = new byte[dataBuffer.readableByteCount()];
                                dataBuffer.read(content);
                                DataBufferUtils.release(dataBuffer);//释放掉内存
                                // 构建日志
                                List<Object> rspArgs = new ArrayList<>();
                                rspArgs.add(originalResponse.getStatusCode());
                                String data = new String(content, StandardCharsets.UTF_8);
                                // 响应日志
                                log.info("\n接口响应成功!\ndata==>{}\nrpsArgs==>{}"
                                        , data, rspArgs.toArray());
                                // 调用成功，接口调用次数 + 1
                                try {
                                    gatewayService.invokeApi(interfaceInvokeRequest);
                                } catch (Exception e) {
                                    log.error("调用接口次数加1 失败,用户id:{}\t接口id:{}"
                                            , interfaceInvokeRequest.getUserId()
                                            , interfaceInvokeRequest.getInterfaceId(), e);
                                }
                                return bufferFactory.wrap(content);
                            });
                            return super.writeWith(flux);
                        } else {
                            log.error("<--- {} 响应code异常", getStatusCode());
                        }
                        return super.writeWith(body);
                    }
                };
                return chain.filter(exchange.mutate().response(decoratedResponse).build());
            }
            return chain.filter(exchange);
        } catch (Exception e) {
            //调用失败，返回一个规范的错误码
            log.error(e.getMessage());
            return handleBadRequest(exchange.getResponse());
        }
    }

    private Mono<Void> handleNoAuth(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.setComplete();
    }

    private Mono<Void> handleBadRequest(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.BAD_REQUEST);
        return response.setComplete();
    }
}
